package ferp.core.ai.nn;

import com.google.gson2.Gson;
import com.google.gson2.JsonSyntaxException;
import ferp.center.network.RequestBidAdd;
import ferp.core.ai.nn.nen.Nen;
import ferp.core.calc.scorer.Passing;
import ferp.core.card.Card;
import ferp.core.game.Bid;
import ferp.core.game.Game;
import ferp.core.game.Settings;
import ferp.core.player.Hand;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * User: igorgok
 * Date: 12/9/12 1:45 PM
 */
public class Adaptation
{
  public static Nen createBidNN(int hls) {return new Nen(Nen.Type.Regression, BID_NN_NUM_OF_INPUTS, hls, BID_NN_NUM_OF_OUTPUTS);}
  public static Nen createBidNN(Gson gson, String nnj)
  {
    try
    {
      return gson.fromJson(nnj, Nen.class);
    }
    catch (JsonSyntaxException e)
    {
      return null;
    }
  }

  public static int getPenalty(Game g, Settings settings) {return Passing.getTrickCost(g, settings);}
  public static char getLastBid(int hand, Bid gameCurrentBid) {return (hand == 0 ? RequestBidAdd.NONE : (gameCurrentBid == null ? RequestBidAdd.PASS : RequestBidAdd.type(gameCurrentBid.type)));}

  public static char getResult(float r) {return r <= 0 ? RequestBidAdd.PASS : RequestBidAdd.PLAY;}

  // create 24 NN inputs from the bid data
  public static void bid2nnt(List<float[]> inputs, List<float[]> outputs, int cards, int hand, int st, char lb, int penalty, char result)
  {
    float[] suits = {suit2input(cards, Card.Suit.SPADES), suit2input(cards, Card.Suit.CLUBS), suit2input(cards, Card.Suit.DIAMONDS), suit2input(cards, Card.Suit.HEARTS)};
    List<float[]> perms = permutate(suits);
    float hi = hand2input(hand), sti = st2input(st), lbi = lb2input(lb), pi = penalty2input(penalty, 4);

    // for each cards in hand permutation
    for (float[] s : perms)
    {
      float[] in = new float[BID_NN_NUM_OF_INPUTS];
      int i = 0;

      // cards
      in[i++] = s[0];
      in[i++] = s[1];
      in[i++] = s[2];
      in[i++] = s[3];
      // hand
      in[i++] = hi;
      // starting tricks
      in[i++] = sti;
      // last bid: PASS, PLAY, MISERE, NONE
      in[i++] = lbi;
      // penalty
      in[i] = pi;

      // add to input set
      inputs.add(in);
      // output
      outputs.add(bid2nno(result));
    }
  }

  // create 24 NN inputs from the bid data
  public static double[] bid2nnt(int cards, int hand, int st, char lb, int penalty, char result)
  {
    double[] sample = new double[32 + 4 + 1];
    int[] cbs = {Hand.get(cards, Card.Suit.SPADES), Hand.get(cards, Card.Suit.CLUBS), Hand.get(cards, Card.Suit.DIAMONDS), Hand.get(cards, Card.Suit.HEARTS)};
    int i = 0;

    Arrays.sort(cbs);

    // cards in hand
    for (int s : cbs)
      for (int c = 128; c > 0; c >>>= 1)
        sample[i++] = (s & c) != 0 ? 1 : 0;

    // hand
    sample[i++] = hand2input01(hand);
    // starting tricks
    sample[i++] = st2input01(st);
    // last bid: PASS, PLAY, MISERE, NONE
    sample[i++] = lb2input01(lb);
    // penalty
    sample[i++] = penalty2input01(penalty, 6);
    // output
    sample[i] = bid2nno01(result);

    return sample;
  }

  // create an exact NN input from the bid data
  public static float[] bid2nni(int cards, int hand, int st, char lb, int penalty)
  {
    float[] in = new float[BID_NN_NUM_OF_INPUTS];
    int i = 0;

    // cards
    in[i++] = suit2input(cards, Card.Suit.SPADES);
    in[i++] = suit2input(cards, Card.Suit.CLUBS);
    in[i++] = suit2input(cards, Card.Suit.DIAMONDS);
    in[i++] = suit2input(cards, Card.Suit.HEARTS);
    // hand
    in[i++] = hand2input(hand);
    // starting tricks
    in[i++] = st2input(st);
    // last bid: PASS, PLAY, MISERE, NONE
    in[i++] = lb2input(lb);
    // penalty
    in[i] = penalty2input(penalty, 4);

    return in;
  }

  public static float[] bid2nno(char result)
  {
    float[] out = new float[BID_NN_NUM_OF_OUTPUTS];

    // output: PASS or PLAY (misere is considered PASS!)
    out[0] = (result == RequestBidAdd.PLAY ? 1 : -1);

    return out;
  }

  public static float bid2nno01(char result)
  {
    // output: PASS or PLAY (misere is considered PASS!)
    return (result == RequestBidAdd.PLAY ? 1 : 0);
  }

  // bid NN configuration
  // cards(4) + #hand(1) + starting tricks(1) + last bid(1) + penalty(1)
  private static final int BID_NN_NUM_OF_INPUTS = 4 + 1 + 1 + 1 + 1;
  // bid result(1)
  private static final int BID_NN_NUM_OF_OUTPUTS = 1;

  // whist1 NN configuration
  // cards(4) + contract suit(1) + contract tricks(1)
  private static final int WHIST_NN1_NUM_OF_INPUTS = 0;
  // whist result(1)
  private static final int WHIST_NN1_NUM_OF_OUTPUTS = 1;

  // whist2 NN configuration
  // cards(4) + #hand(1) + contract suit(1) + starting tricks(1) + last bid(1) + penalty(1)
  private static final int WHIST_NN2_NUM_OF_INPUTS = 0;
  // whist result(1)
  private static final int WHIST_NN2_NUM_OF_OUTPUTS = 1;

  private static float _assert(float f)
  {
    if (f < -1 || f > 1)
      throw new AssertionError("input value " + f + " is not in range [-1..1]");

    return f;
  }

  private static float suit2input(int cards, Card.Suit suit)
  {
    float hash = Hand.get(cards, suit);

    // [0..255] -> [-1..1]
    return _assert(hash * 2 / 255 - 1);
  }

  private static float hand2input(int hand)
  {
    switch (hand)
    {
      case 0:  return -1;
      case 1:  return 0;
    }

    return 1;
  }

  private static double hand2input01(int hand)
  {
    switch (hand)
    {
      case 0:  return 0;
      case 1:  return 0.5;
    }

    return 1;
  }

  private static float st2input(int st)
  {
    switch (st)
    {
      case 6:  return -1;
      case 7:  return 0;
    }

    return 1;
  }

  private static float st2input01(int st)
  {
    switch (st)
    {
      case 6:  return 0;
      case 7:  return 0.5f;
    }

    return 1;
  }

  private static float lb2input(char lb)
  {
    switch (lb)
    {
      case RequestBidAdd.PASS:  return -0.33f;
      case RequestBidAdd.PLAY:  return 0.33f;
      case RequestBidAdd.MISERE:  return 1;
    }

    // NONE
    return -1;
  }

  private static double lb2input01(char lb)
  {
    switch (lb)
    {
      case RequestBidAdd.PASS:  return 0.33;
      case RequestBidAdd.PLAY:  return 0.66;
      case RequestBidAdd.MISERE:  return 1;
    }

    // NONE
    return 0;
  }

  private static float penalty2input(int penalty, int limit)
  {
    // limit by 'limit'
    if (penalty > limit)
      penalty = limit;

    // [1..limit] -> [-1..1]
    return _assert((penalty - 1) * 2 / (limit - 1) - 1);
  }

  private static double penalty2input01(int penalty, int limit)
  {
    // limit by 'limit'
    if (penalty > limit)
      penalty = limit;

    // [1..limit] -> [0..1]
    return (double)(penalty - 1) / (double)(limit - 1);
  }

/*
  private static float result2output(char result)
  {
    return (result == RequestBidAdd.PLAY ? 1 : -1);
  }
*/

  private static LinkedList<float[]> permutate(float[] array)
  {
    LinkedList<float[]> results = new LinkedList<float[]>();

    permutate(array, 0, results);

    return results;
  }

  private static void permutate(float[] array, int index, LinkedList<float[]> results)
  {
    if (index != array.length)
      for (int i = index; i < array.length; ++i)
      {
        swap(array, index, i);
        permutate(array, index + 1, results);
        swap(array, i, index);
      }
    else
      results.add(array.clone());
  }

  private static void swap(float[] array, int i1, int i2)
  {
    float temp = array[i1];

    array[i1] = array[i2];
    array[i2] = temp;
  }

/*
  // permutation test
  public static void main(String[] args)
  {
    float[] a = {1, 2, 3, 4};
    int i = 0;
    LinkedList<float[]> r = permutate(a);

    for (float[] f : r)
    {
      System.out.print("#" + (i++) + " ");

      for (float n : f)
        System.out.print("" + n + " ");

      System.out.println();
    }
  }
*/
}
